package space.misiro.ledgers.keycloak.client.impl

import org.slf4j.Logger
import org.slf4j.LoggerFactory
import org.springframework.beans.factory.annotation.Value
import org.springframework.http.HttpStatus
import org.springframework.security.access.AccessDeniedException
import org.springframework.stereotype.Service
import space.misiro.ledgers.keycloak.client.api.KeycloakTokenService
import space.misiro.ledgers.keycloak.client.mapper.KeycloakAuthMapper
import space.misiro.ledgers.keycloak.client.model.TokenConfiguration
import space.misiro.ledgers.keycloak.client.rest.KeycloakTokenRestClient
import space.misiro.ledgers.middleware.api.domain.um.BearerTokenTO

@Service
class KeycloakTokenServiceImpl(
    private val keycloakTokenRestClient: KeycloakTokenRestClient, private val authMapper: KeycloakAuthMapper
) : KeycloakTokenService {

    private val log: Logger = LoggerFactory.getLogger(KeycloakAdminServiceImpl::class.java)

    @Value("\${keycloak.resource:}")
    private lateinit var clientId: String

    @Value("\${keycloak.credentials.secret:}")
    private lateinit var clientSecret: String

    /**
     * 使用用户名密码进行OAuth2密码模式认证，获取Bearer令牌
     *
     * @param username 用户身份标识（通常为用户名或邮箱）
     * @param password 用户密码凭证
     * @return 包含访问令牌和刷新令牌的传输对象
     * @throws [RuntimeException] 当响应状态码非200或响应体为空时可能抛出异常
     */
    override fun login(username: String, password: String) = keycloakTokenRestClient.login(
        // 构建符合OAuth2密码模式要求的请求参数
        mapOf(
            GRANT_TYPE_KEY to "password",
            "username" to username,
            PASSWORD_KEY to password,
            CLIENT_ID_KEY to clientId,
            CLIENT_SECRET_KEY to clientSecret
        )
    ).run {
        // 处理非200状态响应：记录错误日志但不会中断程序流程
        takeUnless { statusCode == HttpStatus.OK }?.also { log.error("Could not obtain token by user credentials [$username]") }

        // 转换成功响应：从响应体中提取核心令牌字段
        body!!.let {
            BearerTokenTO(
                accessToken = it[ACCESS_TOKEN_KEY].toString(), refreshToken = it[REFRESH_TOKEN_KEY].toString()
            )
        }
    }

    /**
     * 使用旧令牌交换新Bearer令牌
     *
     * @param oldToken 需要被交换的旧访问令牌
     * @param timeToLive 新令牌的有效时间（单位：秒）
     * @param scope 新令牌的访问权限范围
     * @return 包含新令牌信息的传输对象
     * @throws AccessDeniedException 当旧令牌无效或交换失败时抛出
     */
    override fun exchangeToken(oldToken: String, timeToLive: Int, scope: String) =
        // 调用令牌交换接口并处理响应结果
        keycloakTokenRestClient.exchangeToken("Bearer $oldToken", TokenConfiguration(timeToLive, scope)).run {
            // 验证并返回有效令牌（空值场景已在validate方法中处理）
            validate(body?.token ?: "")
        }

    /**
     * 验证令牌有效性并解析为标准化格式
     *
     * @param token 需要验证的令牌字符串
     * @return 包含令牌详细信息的传输对象
     * @throws AccessDeniedException 当令牌已过期或被撤销时抛出
     */
    override fun validate(token: String) = keycloakTokenRestClient.validate(
        mapOf(
            "token" to token, CLIENT_ID_KEY to clientId, CLIENT_SECRET_KEY to clientSecret
        )
    ).run {
        // 处理非200 HTTP状态码：记录错误日志但不中断流程
        takeUnless { it.statusCode == HttpStatus.OK }?.also { log.error("Could not validate token") }
        // 检查令牌激活状态：未激活时立即抛出访问拒绝异常
        takeIf { it.body?.otherClaims["active"] == false }?.also { throw AccessDeniedException("Token Expired") }
        // 将验证成功的响应映射为Bearer认证对象
        authMapper.toBearer(body!!, token)
    }

    /**
     * 使用刷新令牌获取新的 Bearer 令牌
     *
     * @param refreshToken 用于刷新访问令牌的凭证令牌。该令牌应当是由认证服务器先前颁发的有效刷新令牌
     * @return BearerTokenTO 包含新生成的访问令牌和刷新令牌的传输对象。注意返回的刷新令牌可能与参数不同（新令牌）
     * @throws AccessDeniedException 当认证服务器返回非200状态码时抛出，表示刷新令牌无效或请求被拒绝
     */
    override fun refreshToken(refreshToken: String) = keycloakTokenRestClient.login(
        mapOf(
            GRANT_TYPE_KEY to "refresh_token",
            CLIENT_ID_KEY to clientId,
            CLIENT_SECRET_KEY to clientSecret,
            REFRESH_TOKEN_KEY to refreshToken
        )
    ).run {
        // 处理非200 HTTP状态码响应：记录错误日志并抛出异常
        takeUnless { statusCode == HttpStatus.OK }?.also {
            log.error("Could not obtain token by refresh token [$refreshToken]")
            throw AccessDeniedException("Invalid Refresh token")
        }

        // 解析成功响应体并构造BearerTokenTO对象
        body!!.let {
            BearerTokenTO(
                accessToken = it[ACCESS_TOKEN_KEY].toString(),
                refreshToken = it[REFRESH_TOKEN_KEY].toString()
            )
        }
    }


    companion object {
        /**
         * OAuth 2.0客户端标识符的请求参数键名
         */
        const val CLIENT_ID_KEY = "client_id"

        /**
         * OAuth 2.0客户端密钥的请求参数键名
         */
        const val CLIENT_SECRET_KEY = "client_secret"

        /**
         * OAuth访问令牌的请求参数键名
         */
        const val ACCESS_TOKEN_KEY = "access_token"

        /**
         * OAuth刷新令牌的请求参数键名
         */
        const val REFRESH_TOKEN_KEY = "refresh_token"

        /**
         * OAuth授权类型的请求参数键名（如密码模式/客户端模式）
         */
        const val GRANT_TYPE_KEY = "grant_type"

        /**
         * 密码凭证授权模式中用户密码的请求参数键名
         */
        const val PASSWORD_KEY = "password"
    }
}